package org.reos;

import java.util.ArrayList;

import static org.reos.ReLogSynchronizer.FrameType.SYNC;
import static org.reos.ReLogSynchronizer.SynchronizerState.*;

/**
 * Created with IntelliJ IDEA.
 * User: Alexander Rogin
 * Date: 26.12.13
 * Time: 15:06
 * To change this template use File | Settings | File Templates.
 */
public class ReLogSynchronizer {
    private static final FrameType[] FRAME_TYPE_MAP = {
            SYNC,
            FrameType.SHORT_2, FrameType.SHORT_2, FrameType.SHORT_2, FrameType.SHORT_2,
            FrameType.SHORT_4, FrameType.SHORT_4, FrameType.SHORT_4, FrameType.SHORT_4,
            FrameType.SHORT_8, FrameType.SHORT_8, FrameType.SHORT_8, FrameType.SHORT_8,
            FrameType.SHORT_16, FrameType.SHORT_16,
            FrameType.EXTENDED
    };

    private SynchronizerState state;
    private byte previousFrameId;
    private FrameType currentFrameType;
    private int numberOfExpectedBytes;
    private ArrayList<ReLogByte> receivedBytes;
    private SyncDetector detector;

    enum SynchronizerState {
        HEADER_BYTE_AWAIT, LENGTH_BYTE_AWAIT, DATA_BYTE_AWAIT
    }
    enum FrameType {
        SYNC(0), SHORT_2(2), SHORT_4(4), SHORT_8(8), SHORT_16(16), EXTENDED(-1);
        private int numVal;
        private FrameType(int numVal) {
            this.numVal = numVal;
        }
        public int getNumVal() {
            return numVal;
        }
    }

    public ReLogSynchronizer() {
        state = HEADER_BYTE_AWAIT;
        previousFrameId = 0x00;
        currentFrameType = null;
        numberOfExpectedBytes = 0;
        receivedBytes = new ArrayList<ReLogByte>();
        detector = new SyncDetector();
    }

    public ArrayList<ReLogByte> processIncomingByte(ReLogByte reLogByte) {
        byte byteValue = reLogByte.getByteValue();
        switch (state) {
            case HEADER_BYTE_AWAIT:
                boolean synced;
                receivedBytes.clear();
                synced = (previousFrameId == getPrevFieldId(byteValue));
                ArrayList<ReLogByte> ret = null;
                if (!synced) {
                    ret = detector.processByte(reLogByte);
                }
                if (synced || ret != null) {
                    previousFrameId = getCurrentFrameId(byteValue);
                    currentFrameType = getFrameType(byteValue);
                    numberOfExpectedBytes = currentFrameType.getNumVal();
                    switch (currentFrameType) {
                        case EXTENDED:
                            state = LENGTH_BYTE_AWAIT;
                            break;
                        case SYNC:
                            state = HEADER_BYTE_AWAIT;
                            break;
                        default:
                            state = DATA_BYTE_AWAIT;
                    }
                }
                return ret;

            case DATA_BYTE_AWAIT:
                receivedBytes.add(reLogByte);
                if (receivedBytes.size() == numberOfExpectedBytes) {
                    state = HEADER_BYTE_AWAIT;
                    return receivedBytes;
                }
                break;
            case LENGTH_BYTE_AWAIT:
                numberOfExpectedBytes = byteValue;
                state = DATA_BYTE_AWAIT;
                break;
        }
        return null;
    }

    private byte getCurrentFrameId(byte b) {
        return (byte) ((b >> 4) & 0x0F);
    }
    private byte getPrevFieldId(byte b) {
        return (byte) (b & 0x0F);
    }
    private FrameType getFrameType(byte b) {
        return FRAME_TYPE_MAP[(b >> 4) & 0x0F];
    }

    private class SyncDetector {
        private ArrayList<ReLogByte> byteQueue;
        public SyncDetector() {
            byteQueue = new ArrayList<ReLogByte>();
        }
        public ArrayList<ReLogByte> processByte(ReLogByte b) {
            byte byteValue = b.getByteValue();
            if (byteValue == 0x00 || byteValue == 0xFF) {
                byteQueue.add(b);
                return null;
            }
            int size = byteQueue.size();
            for (int i = 0; i < size; i++) {
                byte current = byteQueue.get(i).getByteValue();
                int expectedLength = getFrameType(current).getNumVal();
                if (expectedLength < 0) {
                    if (i < size - 1) {
                        expectedLength = byteQueue.get(i + 1).getByteValue();
                    } else {
                        continue;
                    }
                }
                if (expectedLength + i + 1 != size) {
                    continue;
                }
                if (getCurrentFrameId(current) == getPrevFieldId(byteValue)) {
                    i += (getFrameType(current) == FrameType.EXTENDED) ? 2 : 1;
                    ArrayList<ReLogByte> data = new ArrayList<ReLogByte>();
                    for (; i < size; i++) {
                        data.add(byteQueue.get(i));
                    }
                    byteQueue.clear();
                    //System.out.println("SyncDetector Synced!!!");
                    return data;
                }
            }
            byteQueue.add(b);
            return null;
        }
    }
}
